#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <string.h>
#include <ctype.h>
#include "analisadorLexico.h"
#include "buffer.h"
#include "gerenErros.h"
#include "tabelaSimbolos.h"

//Guarda o valor do caracter lido no buffer.
char caracter;
//Representa o estado atual em que o automato se encontra.
int estado;
//Flag para controlar quando se deve terminar um lexema.
int flagLexema;
//Guarda o numero da linha atual.
int linha = 1;
//Lexema que esta sendo criado pelo Analisador Lexico.
char *lexema;
//Guarda o TAMANHO ATUAL do buffer de lexema.
int tamBuffLexema;
//Primeira posicao livre apos o ultimo caracter presente no buffer.
int ponteiroLexema;


//Retorna o numero da linha em que se encontra o caracter.
int getLinha() {
    return linha;
}

//Retorna o lexema.
char* getLexema() {
    return lexema;
}

//Altera a variavel 'estado' para o valor que representa
//o estado inicial do automato.
void configuraEstadoInicial() {
    estado = ESTADO_INICIAL;
}

//Inicializa o buffer de lexemas para uma nova entrada.
void iniciaLexema() {
    flagLexema = 1;
    inicializaLexema();
}

//Finaliza o lexema adicionando um \0 ao final
//da cadeia de caracteres aberta em iniciaLexema.
//Ao mesmo tempo libera a memoria do buffer de lexemas
//e seta a flag de lexema aberto como 0.
void terminaLexema() {
    flagLexema = 0;
}

//Teste se o Token representa uma palavra reservada da linguagem.
int palavraReservada(int token) {
    return token >= TYPEDEF && token <= FALSE;
}

//Armazena o primeiro caracter para dar inicio ao automato.
void inicializaAnalisadorLexico() {
    caracter = getProximoChar();
}

//Aloca memoria para o lexema.
void inicializaLexema() {
    lexema = (char*) malloc(sizeof(char) * TAM_BUFF_LEX);
    if(lexema == NULL) free(lexema);
    tamBuffLexema = TAM_BUFF_LEX;
    ponteiroLexema = 0;
}

//Adiciona um caracter na primeira posicao livre do buffer.
//Se o tamanho do buffer for maior que o ponteiro,
//o tamanho do buffer cresce linearmente a uma taxa definida
//pela constante TAM_CRESC.
void adicionaCaracter(char c) {
    if(ponteiroLexema == tamBuffLexema - 1) {
        tamBuffLexema = tamBuffLexema + TAM_CRESC;
        lexema = (char*) realloc(lexema, tamBuffLexema * sizeof(char));
        if(lexema == NULL) free(lexema);
    }
    lexema[ponteiroLexema] = c;
    ponteiroLexema++;
}

//Adiciona o caracter que separa os lexemas.
void finalizaLexema() {
    adicionaCaracter('\0');
}

//"Desaloca" a memoria ocupada pelo buffer de lexema fazendo
//uso de uma funcao do proprio C (free).
void freeLexema() {
    free(lexema);
}

//Retorna o nome do Token para impressao.
char* nomeToken(int token) {
     switch(token) {
        case TOKEN_REST : return "REST";
        case TOKEN_AND : return "AND";
        case TOKEN_BINARYAND : return "BINARYAND";
        case TOKEN_DIV : return "DIV";
        case TOKEN_LBRACE : return "LBRACE";
        case TOKEN_RBRACE : return "RBRACE";
        case TOKEN_ID : return "ID";
        case TOKEN_LPAREN : return "LPAREN";
        case TOKEN_RPAREN : return "RPAREN";
        case TOKEN_MINUS : return "MINUS";
        case TOKEN_PLUS : return "PLUS";
        case TOKEN_MULTIPLICATION : return "MULTIPLICATION";
        case TOKEN_LITERAL : return "LITERAL";
        case TOKEN_ASSIGN : return "ASSIGN";
        case TOKEN_COMPAREEQUAL : return "COMPAREEQUAL";
        case TOKEN_LESSTHAN : return "LESSTHAN";
        case TOKEN_GREATERTHAN : return "GREATERTHAN";
        case TOKEN_GREATEREQUAL : return "GREATEREQUAL";
        case TOKEN_LESSEQUAL : return "LESSEQUAL";
        case TOKEN_EOF : return "EOF";
        case TOKEN_OR : return "OR";
        case TOKEN_BINARYOR : return "BINARYOR";
        case TOKEN_LEFTSQUAREBRACKET : return "LEFTSQUAREBRACKET";
        case TOKEN_RIGHTSQUAREBRACKET : return "RIGHTSQUAREBRACKET";
        case TOKEN_COLON : return "COLON";
        case TOKEN_COMMA : return "COMMA";
        case TOKEN_SEMICOLON : return "SEMICOLON";
        case TOKEN_NOTEQ : return "NOTEQ";
        case TOKEN_UNARYNEGATION : return "UNARYNEGATION";
        case TOKEN_NUM : return "NUM";
        case TOKEN_DOT : return "DOT";
        case TYPEDEF : return "TYPEDEF";
        case STRUCT : return "STRUCT";
        case INT : return "INT";
        case FLOAT : return "FLOAT";
        case BOOLEAN : return "BOOLEAN";
        case IF : return "IF";
        case ELSE : return "ELSE";
        case WHILE : return "WHILE";
        case SWITCH : return "SWITCH";
        case BREAK : return "BREAK";
        case PRINTLN : return "PRINTLN";
        case READLN : return "READLN";
        case RETURN : return "RETURN";
        case CASE : return "CASE";
        case TRUE : return "TRUE";
        case FALSE : return "FALSE";
        default : return "";
    }
}


//Metodo de impressao de tokens.
void imprimeToken(int token) {
    char* impressao;
    impressao = nomeToken(token);
    if(flagLexema == 1) finalizaLexema();
    return;
}

//Busca o proximo token lendo o proximo caracter,
//consumindo-o, ou nao, alterando o estado do automato
//e retornando um token caso o automato entenda que
//o token terminou e pode ser retornado.
int proximoToken() {
    configuraEstadoInicial();
    while(1) {
        switch(estado) {
            case ESTADO_INICIAL:
                switch(caracter) {
                    case CHAR_ESPACO:
                        caracter = getProximoChar();
                        break;
                    case CHAR_QUEBRA_DE_LINHA:
                        //Incrementa-se o numero da linha.
                        linha++;
                        caracter = getProximoChar();
                        break;
                    case CHAR_RETORNO:
                        caracter = getProximoChar();
                        break;
                    case CHAR_TAB:
                        caracter = getProximoChar();
                        break;
                    case CHAR_MENOS:
                        caracter = getProximoChar();
                        imprimeToken(TOKEN_MINUS);
                        return TOKEN_MINUS;
                    case CHAR_MAIS:
                        caracter = getProximoChar();
                        imprimeToken(TOKEN_PLUS);
                        return TOKEN_PLUS;
                    case CHAR_ASTERISCO:
                        caracter = getProximoChar();
                        imprimeToken(TOKEN_MULTIPLICATION);
                        return TOKEN_MULTIPLICATION;
                    case CHAR_ASPAS:
                        estado = ESTADO_LITERAL;
                        iniciaLexema();
                        adicionaCaracter(caracter);
                        caracter = getProximoChar();
                        break;
                    case CHAR_PORCENTAGEM:
                        imprimeToken(TOKEN_REST);
                        caracter = getProximoChar();
                        return TOKEN_REST;
                    case CHAR_AND:
                        estado = ESTADO_AND;
                        caracter = getProximoChar();
                        break;
                    case CHAR_BARRA:
                        estado = ESTADO_BARRA;
                        caracter = getProximoChar();
                        break;
                    case CHAR_CHAVE_ESQUERDA:
                        caracter = getProximoChar();
                        imprimeToken(TOKEN_LBRACE);
                        return TOKEN_LBRACE;
                    case CHAR_CHAVE_DIREITA:
                        caracter = getProximoChar();
                        imprimeToken(TOKEN_RBRACE);
                        return TOKEN_RBRACE;
                    case CHAR_PARENTESIS_ESQUERDO:
                        caracter = getProximoChar();
                        imprimeToken(TOKEN_LPAREN);
                        return TOKEN_LPAREN;
                    case CHAR_PARENTESIS_DIREITO:
                        caracter = getProximoChar();
                        imprimeToken(TOKEN_RPAREN);
                        return TOKEN_RPAREN;
                    case CHAR_IGUAL:
                        estado = ESTADO_IGUAL;
                        caracter = getProximoChar();
                        break;
                    case CHAR_MENOR:
                        estado = ESTADO_MENOR;
                        caracter = getProximoChar();
                        break;
                    case CHAR_MAIOR:
                        estado = ESTADO_MAIOR;
                        caracter = getProximoChar();
                        break;
                    case CHAR_PIPE:
                        estado = ESTADO_PIPE;
                        caracter = getProximoChar();
                        break;
                    case CHAR_COLCHETE_ESQUERDO:
                        imprimeToken(TOKEN_LEFTSQUAREBRACKET);
                        caracter = getProximoChar();
                        return TOKEN_LEFTSQUAREBRACKET;
                    case CHAR_COLCHETE_DIREITO:
                        imprimeToken(TOKEN_RIGHTSQUAREBRACKET);
                        caracter = getProximoChar();
                        return TOKEN_RIGHTSQUAREBRACKET;
                    case CHAR_DOIS_PONTOS:
                        imprimeToken(TOKEN_COLON);
                        caracter = getProximoChar();
                        return TOKEN_COLON;
                    case CHAR_VIRGULA:
                        imprimeToken(TOKEN_COMMA);
                        caracter = getProximoChar();
                        return TOKEN_COMMA;
                    case CHAR_PONTO_E_VIRGULA:
                        imprimeToken(TOKEN_SEMICOLON);
                        caracter = getProximoChar();
                        return TOKEN_SEMICOLON;
                    case CHAR_EXCLAMACAO:
                        estado = ESTADO_EXCLAMACAO;
                        caracter = getProximoChar();
                        break;
                    case CHAR_PONTO:
                        estado = ESTADO_PONTO;
                        caracter = getProximoChar();
                        break;
                    //Cases para todos os valores alpha (letras).
                    //Como nao e possivel realizar chamadas a funcoes,
                    //em um label switch, os cases abaixo aninhados
                    //e o case de '0' ... '9' realizam algo como:
                    //"case isdigit(caracter):"
                    //"case isalpha(caracter):"
                    case 'A' ... 'Z':
                    case 'a' ... 'z':
                        estado = ESTADO_ID;
                        iniciaLexema();
                        adicionaCaracter(caracter);
                        caracter = getProximoChar();
                        break;
                    case '0' ... '9':
                        estado = ESTADO_DIGITO;
                        iniciaLexema();
                        adicionaCaracter(caracter);
                        caracter = getProximoChar();
                        break;
                    case EOF:
                        imprimeToken(TOKEN_EOF);
                        return TOKEN_EOF;
                    default:
                        erroLexico(ERRO_CARACTER_INVALIDO, linha);
                        caracter = getProximoChar();
                        return -1;
                }
                break; //break ESTADO_INICIAL
            case ESTADO_AND:
                if(caracter == '&') {
                    imprimeToken(TOKEN_AND);
                    caracter = getProximoChar();
                    return TOKEN_AND;
                } else {
                    imprimeToken(TOKEN_BINARYAND);
                    return TOKEN_BINARYAND;
                }
            case ESTADO_BARRA:
                if(caracter == CHAR_ASTERISCO) {
                    estado = ESTADO_COMENTARIO;
                    caracter = getProximoChar();
                    break;
                } else {
                    imprimeToken(TOKEN_DIV);
                    configuraEstadoInicial();
                    return TOKEN_DIV;
                }
            case ESTADO_COMENTARIO:
                if(caracter == CHAR_ASTERISCO) {
                    estado = ESTADO_COMENTARIO_FIM;
                    caracter = getProximoChar();
                    break;
                } else if(caracter == CHAR_QUEBRA_DE_LINHA) {
                    linha++;
                    caracter = getProximoChar();
                    break;
                } else if(caracter == EOF){
                    erroLexico(ERRO_EOF_INESPERADO, linha);
                    return -1;
                } else {
                    caracter = getProximoChar();
                    break;
                }
            case ESTADO_COMENTARIO_FIM:
                if(caracter == CHAR_BARRA) {
                    configuraEstadoInicial();
                    caracter = getProximoChar();
                    break;
                } else if(caracter == CHAR_ASTERISCO) {
                    caracter = getProximoChar();
                    break;
                } else if(caracter == EOF) {
                    erroLexico(ERRO_EOF_INESPERADO, linha);
                    return -1;
                } else if(caracter == CHAR_QUEBRA_DE_LINHA) {
                    linha++;
                    estado = ESTADO_COMENTARIO;
                    caracter = getProximoChar();
                    break;
                } else {
                    estado = ESTADO_COMENTARIO;
                    caracter = getProximoChar();
                    break;
                }
            case ESTADO_ID:
                if(isalpha(caracter) || isdigit(caracter)) {
                    adicionaCaracter(caracter);
                    caracter = getProximoChar();
                    break;
                } else {
                    finalizaLexema();
                    int tokenAdicionado = adicionaLexema(getLexema(), TOKEN_ID, SEM_ESCOPO, SEM_TIPO, NAO_EH_FUNCAO, "$global", SIMBOLO_INATIVO, NAO_EH_TYPEDECL, NAO_EH_ARRAY);
                    imprimeToken(tokenAdicionado);
                    terminaLexema();
                    return tokenAdicionado;
                }
            case ESTADO_LITERAL:
                if(caracter == CHAR_ASPAS) {
                    adicionaCaracter(caracter);
                    imprimeToken(TOKEN_LITERAL);
                    adicionaLexema(getLexema(), TOKEN_LITERAL, SEM_ESCOPO, SEM_TIPO, NAO_EH_FUNCAO, "$global", SIMBOLO_INATIVO, NAO_EH_TYPEDECL, NAO_EH_ARRAY);
                    terminaLexema();
                    caracter = getProximoChar();
                    return TOKEN_LITERAL;
                } else if(caracter == EOF) {
                    terminaLexema();
                    erroLexico(ERRO_EOF_INESPERADO, linha);
                    return -1;
                } else if(caracter == CHAR_QUEBRA_DE_LINHA) {
                    //Literais podem conter quebra de linha!
                    linha++;
                    adicionaCaracter(caracter);
                    caracter = getProximoChar();
                    break;
                } else {
                    adicionaCaracter(caracter);
                    caracter = getProximoChar();
                    break;
                }
            case ESTADO_IGUAL:
                if(caracter == CHAR_IGUAL) {
                    imprimeToken(TOKEN_COMPAREEQUAL);
                    caracter = getProximoChar();
                    return TOKEN_COMPAREEQUAL;
                } else {
                    imprimeToken(TOKEN_ASSIGN);
                    return TOKEN_ASSIGN;
                }
            case ESTADO_MENOR:
                if(caracter == CHAR_IGUAL){
                    imprimeToken(TOKEN_LESSEQUAL);
                    caracter = getProximoChar();
                    return TOKEN_LESSEQUAL;
                } else {
                    imprimeToken(TOKEN_LESSTHAN);
                    return TOKEN_LESSTHAN;
                }
            case ESTADO_MAIOR:
                if(caracter == CHAR_IGUAL){
                    imprimeToken(TOKEN_GREATEREQUAL);
                    caracter = getProximoChar();
                    return TOKEN_GREATEREQUAL;
                } else {
                    imprimeToken(TOKEN_GREATERTHAN);
                    return TOKEN_GREATERTHAN;
                }
            case ESTADO_PIPE:
                if(caracter == CHAR_PIPE) {
                    imprimeToken(TOKEN_OR);
                    caracter = getProximoChar();
                    return TOKEN_OR;
                } else {
                    imprimeToken(TOKEN_BINARYOR);
                    return TOKEN_BINARYOR;
                }
            case ESTADO_EXCLAMACAO:
                if(caracter == CHAR_IGUAL) {
                    imprimeToken(TOKEN_NOTEQ);
                    caracter = getProximoChar();
                    return TOKEN_NOTEQ;
                } else {
                    imprimeToken(TOKEN_UNARYNEGATION);
                    return TOKEN_UNARYNEGATION;
                }
            case ESTADO_PONTO:
                if(isdigit(caracter)) {
                    estado = ESTADO_PONTO_DIGITO;
                    iniciaLexema();
                    adicionaCaracter(caracter);
                    caracter = getProximoChar();
                    break;
                } else {
                    imprimeToken(TOKEN_DOT);
                    return TOKEN_DOT;
                }
            case ESTADO_PONTO_DIGITO:
                if(caracter == CHAR_E || caracter == CHAR_E_MINUSCULO) {
                    estado = ESTADO_E;
                    adicionaCaracter(caracter);
                    caracter = getProximoChar();
                    break;
                } else if(isdigit(caracter)) {
                    adicionaCaracter(caracter);
                    caracter = getProximoChar();
                    break;
                } else {
                    imprimeToken(TOKEN_NUM);
                    terminaLexema();
                    return TOKEN_NUM;
                }
            case ESTADO_DIGITO:
                if(caracter == CHAR_E || caracter == CHAR_E_MINUSCULO) {
                    estado = ESTADO_E;
                    adicionaCaracter(caracter);
                    caracter = getProximoChar();
                    break;
                } else if(isdigit(caracter)) {
                    adicionaCaracter(caracter);
                    caracter = getProximoChar();
                    break;
                } else if(caracter == CHAR_PONTO) {
                    estado = ESTADO_PONTO_DIGITO;
                    adicionaCaracter(caracter);
                    caracter = getProximoChar();
                    break;
                } else {
                    imprimeToken(TOKEN_NUM);
                    terminaLexema();
                    return TOKEN_NUM;
                }
            case ESTADO_E:
                if(caracter == CHAR_MAIS || caracter == CHAR_MENOS) {
                    estado = ESTADO_SINAL;
                    adicionaCaracter(caracter);
                    caracter = getProximoChar();
                    break;
                } else if (isdigit(caracter)) {
                    estado = ESTADO_SINAL_DIGITO;
                    adicionaCaracter(caracter);
                    caracter = getProximoChar();
                    break;
                } else {
                    imprimeToken(TOKEN_NUM);
                    terminaLexema();
                    return TOKEN_NUM;
                }
            case ESTADO_SINAL:
                if(isdigit(caracter)) {
                    estado = ESTADO_SINAL_DIGITO;
                    adicionaCaracter(caracter);
                    caracter = getProximoChar();
                    break;
                } else if(caracter == CHAR_QUEBRA_DE_LINHA){
                    erroLexico(ERRO_QUEBRA_LINHA_INESPERADA, linha);
                    linha++;
                    imprimeToken(TOKEN_NUM);
                    terminaLexema();
                    caracter = getProximoChar();
                    return -1;
                } else if(caracter == EOF){
                    erroLexico(ERRO_EOF_INESPERADO, linha);
                    terminaLexema();
                    return -1;
                } else {
                    erroLexico(ERRO_CARACTER_INVALIDO, linha);
                    return -1;
                }
            case ESTADO_SINAL_DIGITO:
                if(isdigit(caracter)) {
                    adicionaCaracter(caracter);
                    caracter = getProximoChar();
                    break;
                } else {
                    imprimeToken(TOKEN_NUM);
                    terminaLexema();
                    return TOKEN_NUM;
                }
        }
    }
}

